package android.framework.widget.scrollview;

import android.content.Context;
import android.content.res.TypedArray;
import android.framework.widget.scrollview.ObservableScrollView.ScrollCallbacks;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ScrollView;

import android.framework.R;

/**
 * 可设置滑倒顶部跟底部的view  setParallaxOffset(int offset)设置视差的效果
 * 
 * @ClassName: ParallaxScrollView
 * @Description: TODO
 * @author jtf19@163.com
 * @date 2013-7-23 下午2:22:01
 * 
 */
public class ParallaxScrollView extends ViewGroup {
	private static final int DEFAULT_CHILD_GRAVITY = Gravity.CENTER_HORIZONTAL;

	private static final String TAG = "ParallaxScrollView";

	private static float PARALLAX_OFFSET_DEFAULT = 0.2f;

	/**
	 * By how much should the background move to the foreground
	 */
	private float mParallaxOffset = PARALLAX_OFFSET_DEFAULT;

	private View mBackground;
	private ObservableScrollView mScrollView;
	private final ScrollCallbacks mScrollCallbacks = new ScrollCallbacks() {
		@Override
		public void onScrollChanged(int l, int t, int oldl, int oldt) {
			requestLayout();
		}
	};

	// Layout stuff

	private int mBackgroundRight;
	private int mBackgroundBottom;
	/**
	 * Height of the Foreground ScrollView Content
	 */
	private int mScrollContentHeight = 0;
	/**
	 * Height of the ScrollView, should be the same as this view
	 */
	private int mScrollViewHeight = 0;
	/**
	 * The multipler by how much to move the background to the foreground
	 */
	private float mScrollDiff = 0f;

	public ParallaxScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
				R.styleable.ParallaxScrollView, 0, 0);

		try {
			setParallaxOffset(a.getFloat(
					R.styleable.ParallaxScrollView_parallexOffset,
					PARALLAX_OFFSET_DEFAULT));
		} catch (Exception e) {
		} finally {
			a.recycle();
		}
	}

	public ParallaxScrollView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ParallaxScrollView(Context context) {
		this(context, null);
	}

	@Override
	public void addView(View child) {
		if (getChildCount() > 1)
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");

		super.addView(child);
	}

	@Override
	public void addView(View child, int index) {
		if (getChildCount() > 1)
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		super.addView(child, index);
	}

	@Override
	public void addView(View child, int index,
			android.view.ViewGroup.LayoutParams params) {
		if (getChildCount() > 1)
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		super.addView(child, index, params);
	}

	@Override
	public void addView(View child, int width, int height) {
		if (getChildCount() > 1)
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		super.addView(child, width, height);
	}

	@Override
	public void addView(View child, android.view.ViewGroup.LayoutParams params) {
		if (getChildCount() > 1)
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		super.addView(child, params);
	}

	/**
	 * Set the offset
	 * 
	 * @param offset
	 *            a number greater than 0.0
	 */
	public void setParallaxOffset(float offset) {
		// Make sure we only get to .05 of a floating number
		offset = (float) Math.rint(offset * 100) / 100;
		if (offset > 0.0)
			mParallaxOffset = offset;
		else
			mParallaxOffset = PARALLAX_OFFSET_DEFAULT;

		requestLayout();
	}

	/**
	 * Get the current offset
	 * 
	 * @return
	 */
	public float getParallaxOffset() {
		return mParallaxOffset;
	}

	/**
	 * Sort the views out after they have been inflated from a layout
	 */
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();

		if (getChildCount() > 2) {
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		}
		organiseViews();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		if (mScrollView != null) {
			measureChild(mScrollView,
					MeasureSpec.makeMeasureSpec(
							MeasureSpec.getSize(widthMeasureSpec),
							MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(
							MeasureSpec.getSize(heightMeasureSpec),
							MeasureSpec.AT_MOST));

			mScrollContentHeight = mScrollView.getChildAt(0)
					.getMeasuredHeight();
			mScrollViewHeight = mScrollView.getMeasuredHeight();

		}
		if (mBackground != null) {
			int minHeight = 0;
			minHeight = (int) (mScrollViewHeight + mParallaxOffset
					* (mScrollContentHeight - mScrollViewHeight));
			minHeight = Math.max(minHeight,
					MeasureSpec.getSize(heightMeasureSpec));

			measureChild(mBackground,
					MeasureSpec.makeMeasureSpec(
							MeasureSpec.getSize(widthMeasureSpec),
							MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
							minHeight, MeasureSpec.EXACTLY));

			mBackgroundRight = getLeft() + mBackground.getMeasuredWidth();
			mBackgroundBottom = getTop() + mBackground.getMeasuredHeight();

			mScrollDiff = (float) (mBackground.getMeasuredHeight() - mScrollViewHeight)
					/ (float) (mScrollContentHeight - mScrollViewHeight);
		}

	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		final int parentLeft = getPaddingLeft();
		final int parentRight = right - left - getPaddingRight();
		final int parentTop = getPaddingTop();
		final int parentBottom = bottom - top - getPaddingBottom();
		if (mScrollView != null && mScrollView.getVisibility() != GONE) {
			final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mScrollView
					.getLayoutParams();

			final int width = mScrollView.getMeasuredWidth();
			final int height = mScrollView.getMeasuredHeight();

			int childLeft;
			int childTop;

			int gravity = lp.gravity;
			if (gravity == -1) {
				gravity = DEFAULT_CHILD_GRAVITY;
			}

			final int horizontalGravity = gravity
					& Gravity.HORIZONTAL_GRAVITY_MASK;
			final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

			switch (horizontalGravity) {
			case Gravity.LEFT:
				childLeft = parentLeft + lp.leftMargin;
				break;
			case Gravity.CENTER_HORIZONTAL:
				childLeft = parentLeft + (parentRight - parentLeft - width) / 2
						+ lp.leftMargin - lp.rightMargin;
				break;
			case Gravity.RIGHT:
				childLeft = parentRight - width - lp.rightMargin;
				break;
			default:
				childLeft = parentLeft + lp.leftMargin;
			}

			switch (verticalGravity) {
			case Gravity.TOP:
				childTop = parentTop + lp.topMargin;
				break;
			case Gravity.CENTER_VERTICAL:
				childTop = parentTop + (parentBottom - parentTop - height) / 2
						+ lp.topMargin - lp.bottomMargin;
				break;
			case Gravity.BOTTOM:
				childTop = parentBottom - height - lp.bottomMargin;
				break;
			default:
				childTop = parentTop + lp.topMargin;
			}

			mScrollView.layout(childLeft, childTop, childLeft + width, childTop
					+ height);

		}

		if (mBackground != null) {
			final int scrollYCenterOffset = -mScrollView.getScrollY();
			final int offset = (int) (scrollYCenterOffset * mScrollDiff);
			// L.d(TAG, "Layout Scroll Y: " + scrollYCenterOffset +
			// " ScrollDiff: " + mScrollDiff
			// + " Background Offset:" + offset);
			mBackground.layout(getLeft(), offset, mBackgroundRight, offset
					+ mBackgroundBottom);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public LayoutParams generateLayoutParams(AttributeSet attrs) {
		return new FrameLayout.LayoutParams(getContext(), attrs);
	}

	@Override
	protected ViewGroup.LayoutParams generateLayoutParams(
			ViewGroup.LayoutParams p) {
		return new FrameLayout.LayoutParams(p);
	}

	@Override
	protected LayoutParams generateDefaultLayoutParams() {
		return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
				LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
		return p instanceof FrameLayout.LayoutParams;
	}

	/**
	 * Take the direct children and sort them
	 */
	private void organiseViews() {
		if (getChildCount() <= 0)
			return;

		if (getChildCount() == 1) {
			// Get the only child
			final View forground = getChildAt(0);
			organiseBackgroundView(null);
			organiseForgroundView(forground);
		} else if (getChildCount() == 2) {
			final View background = getChildAt(0);
			final View foreground = getChildAt(1);

			organiseBackgroundView(background);
			organiseForgroundView(foreground);
		} else {
			throw new IllegalStateException(
					"ParallaxScrollView can host only two direct children");
		}
	}

	private void organiseBackgroundView(final View background) {
		mBackground = background;
	}

	private void organiseForgroundView(final View forground) {
		final int insertPos = getChildCount() - 1;

		// See if its a observable scroll view?
		if (forground instanceof ObservableScrollView) {
			// Attach the callback to it.
			mScrollView = (ObservableScrollView) forground;
		} else if (forground instanceof ViewGroup
				&& !(forground instanceof ScrollView)) {
			// See if it is a view group but not a scroll view and wrap it
			// with an observable ScrollView
			mScrollView = new ObservableScrollView(getContext(), null);
			removeView(forground);
			mScrollView.addView(forground);
			addView(mScrollView, insertPos);
		} else if (forground instanceof ScrollView) {
			final View child;
			if (((ScrollView) forground).getChildCount() > 0)
				child = ((ScrollView) forground).getChildAt(0);
			else
				child = null;

			mScrollView = new ObservableScrollView(getContext(), null);
			removeView(forground);
			if (child != null)
				mScrollView.addView(child);
			addView(mScrollView, insertPos);
		} else if (forground instanceof View) {
			mScrollView = new ObservableScrollView(getContext(), null);
			removeView(forground);
			mScrollView.addView(forground);
			addView(mScrollView, insertPos);

		}
		if (mScrollView != null) {
			mScrollView.setLayoutParams(forground.getLayoutParams());
			mScrollView.setCallbacks(mScrollCallbacks);
			mScrollView.setFillViewport(true);
		}
	}

}